package manage

import (
	"errors"
	"sort"
	"strconv"
	"strings"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/log"
	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/common/util/pb"
	"hundsun.com/hsl/hschain/contract"
	"hundsun.com/hsl/hschain/contract/native/manage/common"
	"hundsun.com/hsl/hschain/contract/native/manage/manageflow"
	execcomm "hundsun.com/hsl/hschain/executor/common"
	"hundsun.com/hsl/hschain/protos"
	pbcommon "hundsun.com/hsl/hschain/protos/common"
	"hundsun.com/hsl/hschain/protos/execute"
	"hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/protos/manage"
	"hundsun.com/hsl/hschain/store/extend/account"
	pbledger "hundsun.com/hsl/hschain/store/extend/ledger"
)

// Reg 注册执行器名称
func Reg(cfg *config.PluginConfig) {
	height := uint64(0)
	if cfg != nil {
		if v, ok := cfg.Parameters["height"]; ok {
			h, err := strconv.ParseInt(v, 10, 64)
			if err == nil && h > 0 {
				height = uint64(h)
			}
		}
	}

	contract.Register(common.NativeManage, newNativeManage, false, true, height)
}

type nativeManage struct{}

func newNativeManage() contract.Contract {
	return &nativeManage{}
}

// Init 初始化
func (nm *nativeManage) Init() {}

// Run 执行manage合约
func (nm *nativeManage) Run(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	tx := gtx.GetTx()
	from := gtx.GetTx().GetFrom()
	acc := account.NewAccountDB(ctx)
	fromAccount, _ := acc.GetAccount(from)
	if fromAccount == nil {
		log.Warnf("from account is not exist, create new addr is %s", from)
		fromAccount = &pbcommon.Account{InternalAddress: from, Type: pbcommon.Account_USER}
		kv, _ := account.KV(fromAccount)
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: types.TyLogGenerate, Data: kv.Value})
	}

	fee := tx.GetFee()
	genesisPayload := pb.GetGenesisTransactionPayload()
	if genesisPayload.GetFeeLimit() != nil && genesisPayload.GetFeeLimit().GetMinTxFee() > 0 {
		if fee < genesisPayload.GetFeeLimit().GetMinTxFee() {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FEE_NOT_ENOUGH
			return gtx, nil
		}
	}

	//更新from账户余额
	if fee > 0 {
		fromBalance := fromAccount.GetBalance()
		fromBalance = fromBalance - fee
		fromAccount.Balance = fromBalance
		kv, _ := account.KV(fromAccount)
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
		gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: types.TyLogFee, Data: kv.Value})
	}

	payload := tx.GetPayload()
	var manageflowPayload = &manage.ManageFlowPayload{}
	err := proto.Unmarshal(payload, manageflowPayload)
	if err != nil {
		return nil, errors.New("unmarshal workflow payload failed")
	}

	defer UpdateAddressRWStatus(gtx)

	param := manageflowPayload.ManageFlowParam
	if createFlowParam, ok := param.(*manage.ManageFlowPayload_CreateFlowParam); ok {
		return nm.runCreateManageFLow(ctx, gtx, createFlowParam)
	} else if createCfgParam, ok := param.(*manage.ManageFlowPayload_CreateCfgParam); ok {
		return nm.runCreateManageFlowCfg(ctx, gtx, createCfgParam)
	} else if voteParam, ok := param.(*manage.ManageFlowPayload_VoteParam); ok {
		return nm.runVoteManageFlow(ctx, gtx, voteParam)

	} else if ModifyManageFlowProposalParam, ok := param.(*manage.ManageFlowPayload_ModifyFlowParam); ok {
		return nm.runModifyManageFlowProposal(ctx, gtx, ModifyManageFlowProposalParam)
	} else if closeParam, ok := param.(*manage.ManageFlowPayload_CloseFlowParam); ok {
		return nm.runCloseManageFlow(ctx, gtx, closeParam)
	}
	return nil, common.ErrUnknownManageFlowType
}

// runCloseManageFlow 根据flowID修改flow
func (nm *nativeManage) runCloseManageFlow(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction,
	param *manage.ManageFlowPayload_CloseFlowParam) (*execute.GroupedTransaction, error) {
	if param.CloseFlowParam == nil {
		log.Error("modProposal param is nil")
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_PARAMETER_NOT_VALID
		return nil, common.ErrNilParam
	}
	flowID := param.CloseFlowParam.GetFlowID()
	flowStatus := &manage.ManageFlowStatus{}
	flowStatusKey := pbledger.GenKey(flowStatus.GetXXXNamespace(), strconv.Itoa(int(flowID)))

	flowStatusRWStatus := &execute.RWStatus{Key: flowStatusKey, RwStatus: execute.RwStatusType_ReadStatus}
	gtx.VarsRWStatus = append(gtx.VarsRWStatus, flowStatusRWStatus)

	statusBytes, err := ctx.Get(flowStatusKey)
	if err != nil {
		log.Errorf("get manage flow status by key %s failed, err: %s", flowStatusKey, err.Error())
		return nil, err
	}
	err = proto.Unmarshal(statusBytes, flowStatus)
	if err != nil {
		return nil, errors.New("unmarshal workflow flowStatus failed")
	}

	if checkOp(ctx, gtx, flowStatus.GetProposal().GetType()) {
		flowStatus.State = manage.ManageFlowState_CloseFlowState
		err = common.UpdateFlowStatus(ctx, gtx, flowStatus, common.TyLogCloseManageFlow)
		if err != nil {
			log.Errorf("update flow status failed, err: %s", err.Error())
			return nil, err
		}
		log.Infof("close manage flow %d successfully", flowID)
		return gtx, nil
	}

	gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_MANAGEFLOW_INSUFFICIENT_PERMISSION
	log.Infof("failed to close manage flow %d because of insufficient permissions", flowID)
	return nil, errors.New("insufficient permissions")
}

// runCreateManageFLow 创建管理流程
func (nm *nativeManage) runCreateManageFLow(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction,
	param *manage.ManageFlowPayload_CreateFlowParam) (*execute.GroupedTransaction, error) {
	flowName := param.CreateFlowParam.GetName()
	if len(flowName) == 0 {
		return nil, common.ErrNilParam
	}

	proposal := param.CreateFlowParam.GetProposal()
	if proposal.GetType() < manage.ProposalType_UserGroupProposalType || proposal.GetType() > manage.ProposalType_ParaChainProposalType {
		return nil, common.ErrUnknownManageFlowProposal
	}

	if checkOp(ctx, gtx, proposal.GetType()) {
		if proposal.GetType() == manage.ProposalType_UserGroupProposalType {
			groupInfo := proposal.GetUserGroup()
			if len(groupInfo.Proposal) == 0 {
				log.Error("user proposal can not be nil")
				gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_PARAMETER_NOT_VALID
				return nil, common.ErrNilParam
			}
			for _, addrInfo := range groupInfo.Proposal {
				addr := strings.Split(addrInfo, ":")[0]
				err := protos.Address(addr).Validate()
				if err != nil {
					log.Errorf("proposal check failed, err: %s", err.Error())
					gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_PARAMETER_NOT_VALID
					return nil, err
				}
			}
		}

		flowInst, err := manageflow.NewFlowInstance(ctx, gtx, flowName, proposal)
		if err != nil {
			log.Errorf("create new flow instance failed, err: %s", err.Error())
			return nil, err
		}

		startHeight := param.CreateFlowParam.GetStartHeight()
		endHeight := param.CreateFlowParam.GetEndHeight()
		if endHeight < startHeight || startHeight < 0 {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FAILED_PARAM_INVALID
			log.Errorf("create flow failed, please check start height and end height")
			return nil, types.ErrInvalidParam
		}

		flowInst.Status.StartHeight = startHeight
		flowInst.Status.EndHeight = endHeight

		log.Infof("create new manage flow successfully, flowID: %d", flowInst.GetCurrentFlowID())

		err = common.UpdateFlowStatus(ctx, gtx, flowInst.GetCurrentStatus(), common.TyLogCreateManageFlow)
		if err != nil {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FAILED_OTHER_REASON
			log.Errorf("update flow current status failed, err: %s", err.Error())
			return nil, err
		}

		err = common.UpdateFlowID(ctx, gtx, flowInst.GetCurrentFlowID())
		if err != nil {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FAILED_OTHER_REASON
			log.Errorf("update flow id failed, err: %s", err.Error())
			return nil, err
		}
		return gtx, nil
	}

	gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_MANAGEFLOW_INSUFFICIENT_PERMISSION
	log.Infof("failed to create manage flow because of insufficient permissions")
	return nil, errors.New("insufficient permissions")
}

// runModifyManageFlowProposal 根据flowID修改flow
func (nm *nativeManage) runModifyManageFlowProposal(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction,
	param *manage.ManageFlowPayload_ModifyFlowParam) (*execute.GroupedTransaction, error) {
	if param.ModifyFlowParam == nil {
		log.Error("modProposal param is nil")
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_PARAMETER_NOT_VALID
		return nil, common.ErrNilParam
	}
	flowID := param.ModifyFlowParam.GetFlowID()
	flowStatus := &manage.ManageFlowStatus{}
	flowStatusKey := pbledger.GenKey(flowStatus.GetXXXNamespace(), strconv.Itoa(int(flowID)))

	nmStatusRWStatus := &execute.RWStatus{Key: flowStatusKey, RwStatus: execute.RwStatusType_ReadStatus}
	gtx.VarsRWStatus = append(gtx.VarsRWStatus, nmStatusRWStatus)

	statusBytes, err := ctx.Get(flowStatusKey)
	if err != nil {
		log.Errorf("get manage flow status by key %s failed, err: %s", flowStatusKey, err.Error())
		return nil, err
	}
	err = proto.Unmarshal(statusBytes, flowStatus)
	if err != nil {
		return nil, errors.New("unmarshal workflow flowStatus failed")
	}

	if checkOp(ctx, gtx, flowStatus.Proposal.GetType()) {
		flowStatus.Proposal = param.ModifyFlowParam.Proposal
		err = common.UpdateFlowStatus(ctx, gtx, flowStatus, common.TyLogModifyManageFlow)
		if err != nil {
			log.Errorf("update flow status failed, err: %s", err.Error())
			return nil, err
		}

		log.Infof("set proposal of  manage flow %d successfully", flowID)
		return gtx, nil
	}

	gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_MANAGEFLOW_INSUFFICIENT_PERMISSION
	log.Infof("failed to modify manage flow proposal due to insufficient permissions")
	return nil, errors.New("insufficient permissions")
}

// runCreateManageFlowCfg 创建管理流程配置
func (nm *nativeManage) runCreateManageFlowCfg(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction,
	param *manage.ManageFlowPayload_CreateCfgParam) (*execute.GroupedTransaction, error) {
	for _, nodes := range param.CreateCfgParam.Cfg.Nodes {
		for _, condition := range nodes.Properties.GetConditions() {
			execConditionTy := condition.Type
			expectKey := condition.ExpectKey
			expectVal := condition.ExpectVal
			lowerBound := condition.LowerBound
			lowerBoundEqual := condition.LowerBoundEqual
			upperBoundEqual := condition.UpperBoundEqual
			upperBound := condition.UpperBound
			boundEqual := condition.BoundEqual
			unit := condition.Unit
			targetKey := condition.TargetKey
			targetVals := condition.TargetVals
			expectPercent := condition.ExpectPercent
			switch execConditionTy {
			case manage.ConditionType_EqualConditionType:
				if expectKey == "" && expectVal == "" {
					return nil, common.ErrInvalidManageFlowCfg
				}
			case manage.ConditionType_RangeConditionType:
				if lowerBound == "" && lowerBoundEqual == "" && upperBoundEqual == "" && upperBound == "" && boundEqual == "" && unit == "" {
					return nil, common.ErrInvalidManageFlowCfg
				}
			case manage.ConditionType_ExistConditionType:
				if targetKey == "" && targetVals == nil {
					return nil, common.ErrInvalidManageFlowCfg
				}
			case manage.ConditionType_PercentConditionType:
				if expectPercent == 0 {
					return nil, common.ErrInvalidManageFlowCfg
				}
			default:
				return nil, common.ErrInvalidManageFlowCfg
			}
		}
	}
	cfgKV, _ := common.CfgKV(param.CreateCfgParam.Cfg)
	ctx.GetStateHandle().SetLocal(cfgKV.Key, cfgKV.Value)
	gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, cfgKV)
	gtx.TxExecRst.Receipt.Logs = append(gtx.TxExecRst.Receipt.Logs, &ledger.Log{Ty: common.TyLogCreateManageFlowCfg, Data: cfgKV.Value})

	nmCfgRWStatus := &execute.RWStatus{Key: cfgKV.GetKey(), RwStatus: execute.RwStatusType_WriteStatus}
	gtx.VarsRWStatus = append(gtx.VarsRWStatus, nmCfgRWStatus)

	return gtx, nil
}

// runVoteManageFlow 执行投票流程
func (nm *nativeManage) runVoteManageFlow(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction,
	param *manage.ManageFlowPayload_VoteParam) (*execute.GroupedTransaction, error) {
	if param.VoteParam == nil {
		log.Error("vote param is nil")
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_PARAMETER_NOT_VALID
		return nil, common.ErrNilParam
	}
	flowID := param.VoteParam.FlowID
	flowInst, err := manageflow.LoadFlowInstanceFromDB(flowID, ctx, gtx)
	if err != nil {
		log.Errorf("load flow instance from db failed, err: %s", err.Error())
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FAILED_OTHER_REASON
		return nil, err
	}

	err = flowInst.Run(ctx, gtx)
	if err != nil {
		log.Errorf("run flow instance failed, err: %s", err.Error())
		return nil, err
	}

	err = common.UpdateFlowStatus(ctx, gtx, flowInst.GetCurrentStatus(), common.TyLogVoteManageFlow)
	if err != nil {
		log.Errorf("update flow status failed, err: %s", err.Error())
		return nil, err
	}

	log.Infof("vote for manage flow %d successfully", flowInst.GetCurrentFlowID())
	return nil, nil
}

func checkOp(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction, proposalType manage.ProposalType) bool {
	validators, _ := common.GetValidators(ctx, gtx, proposalType, false)
	if validators != nil && len(validators.Validators) > 0 {
		if checkValidators(gtx.GetTx().GetFrom(), validators.Validators) {
			return true
		}
	}

	if proposalType != manage.ProposalType_UserGroupProposalType {
		return checkGeneralOp(ctx, gtx)
	}
	return false
}

func checkGeneralOp(ctx *execcomm.ExecContent, gtx *execute.GroupedTransaction) bool {
	generalOpKey := common.GenGroupInfoKey(manage.UserGroupType_GeneralOperator)
	validatorBytes, err := ctx.Get(generalOpKey)
	if err != nil {
		log.Errorf("get general operator info failed, key: %s", generalOpKey)
		return false
	}

	if len(validatorBytes) > 0 {
		var generalOp = &manage.ManageFlowValidatorInfo{}
		err = proto.Unmarshal(validatorBytes, generalOp)
		if err != nil {
			log.Errorf("unmarshal general operator failed, err: %s", err.Error())
			return false
		}
		if generalOp != nil && len(generalOp.Validators) > 0 {
			return checkValidators(gtx.GetTx().GetFrom(), generalOp.Validators)
		}

		generalOpRWStatus := &execute.RWStatus{Key: generalOpKey, RwStatus: execute.RwStatusType_ReadStatus}
		gtx.VarsRWStatus = append(gtx.VarsRWStatus, generalOpRWStatus)
	}
	return false
}

func checkValidators(checkAddr string, validators []string) bool {
	sort.Strings(validators)
	index := sort.SearchStrings(validators, checkAddr)
	if index < len(validators) && validators[index] == checkAddr {
		return true
	}
	return false
}

// UpdateAddressRWStatus 更新地址状态数据
func UpdateAddressRWStatus(gtx *execute.GroupedTransaction) {
	if len(gtx.VarsRWStatus) > 0 {
		var dst = make(map[string]execute.RwStatusType)
		for _, rsStatus := range gtx.VarsRWStatus {
			if status, ok := dst[string(rsStatus.GetKey())]; ok {
				if status == execute.RwStatusType_ReadStatus &&
					rsStatus.GetRwStatus() == execute.RwStatusType_WriteStatus {
					dst[string(rsStatus.GetKey())] = rsStatus.GetRwStatus()
				}
				continue
			}
			dst[string(rsStatus.GetKey())] = rsStatus.RwStatus
		}

		var dstRWStatus []*execute.RWStatus
		for k, v := range dst {
			var rwStatus = &execute.RWStatus{Key: []byte(k), RwStatus: v}
			dstRWStatus = append(dstRWStatus, rwStatus)
		}
		gtx.VarsRWStatus = dstRWStatus
	}
}
